
# 在 Makefile 中的"隐含的", 早先约定了的, 不需要我们再写出来的规则
# "隐含规则"也就是一种惯例, make 会按照这种"惯例"心照不喧地来运行, 那怕我们的 Makefile中没有书写这样的规则
# "隐含规则"会使用一些我们系统变量, 我们可以改变这些系统变量的值来定制隐含规则的运行时的参数
# 如系统变量CFLAGS可以控制编译时的编译器参数

# 我们还可以通过"模式规则"的方式写下自己的隐含规则
# 用"后缀规则"来定义隐含规则会有许多的限制
# 使用"模式规则"会更回得智能和清楚, 但"后缀规则"可以用来保证我们Makefile的兼容性










# 使用隐含规则
foo : foo.o bar.o
	cc -o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)
# 虽然并没有写下如何生成 foo.o 和 bar.o 这两目标的规则和命令
# make 的"隐含规则"功能会自动为我们自动去推导这两个目标的依赖目标和生成命令
# cc –c $(CFLAGS) foo.c
# cc –c $(CFLAGS) bar.c
# 这已经是"约定"好了的事了, make 和我们约定好了用 C 编译器 cc 生成 .o 文件的规则这就是隐含规则
# 当然如果我们为 .o 文件书写了自己的规则, 那么 make 就不会自动推导并调用隐含规则, 它会按照我们写好的规则忠实地执行

# 在 make 的"隐含规则库"中, 每一条隐含规则都在库中有其顺序, 越靠前的则是越被经常使用的
# 所以这会导致我们有些时候即使我们显示地指定了目标依赖, make 也不会管

# 举例
foo.o : foo.p
# 如果目录下存在了 foo.c 文件, 则需要明确指定依赖, 否则默认使用.c文件









# 隐含规则一览
# 可以使用 make 的参数 -r 或 --no-builtin-rules 选项来取消所有的预设置的隐含规则
1. 编译 C 程序的隐含规则
	<n>.o 的目标的依赖目标会自动推导为 <n>.c
	并且其生成命令是 $(CC) -c $(CPPFLAGS) $(CFLAGS)

2. 编译 C++ 程序的隐含规则
	<n>.o 的目标的依赖目标会自动推导为 <n>.cc 或是 <n>.C 
	并且其生成命令是 $(CXX) -c $(CPPFLAGS) $(CFLAGS) (建议使用 .cc 作为 C++ 源文件的后缀, 而不是 .C )

3. 编译 Pascal 程序的隐含规则
	<n>.o 的目标的依赖目标会自动推导为 <n>.p
	并且其生成命令是 $(PC) -c $(PFLAGS)

4. 编译 Fortran/Ratfor 程序的隐含规则
	<n>.o 的目标的依赖目标会自动推导为 <n>.r 或 <n>.F 或 <n>.f
	并且其生成命令是:
	.f $(FC) -c $(FFLAGS)
	.F $(FC) -c $(FFLAGS) $(CPPFLAGS)
	.f $(FC) -c $(FFLAGS) $(RFLAGS)

5. 预处理 Fortran/Ratfor 程序的隐含规则
	<n>.f 的目标的依赖目标会自动推导为 <n>.r 或 <n>.F
	这个规则只是转换 Ratfor 或有预处理的, Fortran 程序到一个标准的 Fortran 程序
	其使用的命令是:
	.F $(FC) -F $(CPPFLAGS) $(FFLAGS)
	.r $(FC) -F $(FFLAGS) $(RFLAGS)

6. 编译 Modula-2 程序的隐含规则
	<n>.sym 的目标的依赖目标会自动推导为 <n>.def
	并且其生成命令是：$(M2C) $(M2FLAGS) $(DEFFLAGS)
	<n>.o 的目标的依赖目标会自动推导为 <n>.mod
	并且其生成命令是：$(M2C) $(M2FLAGS) $(MODFLAGS)

7. 汇编和汇编预处理的隐含规则
	<n>.o 的目标的依赖目标会自动推导为 <n>.s, 默认使用编译器 as
	并且其生成命令是: $(AS) $(ASFLAGS)
	<n>.s 的目标的依赖目标会自动推导为 <n>.S, 默认使用 C 预编译器 cpp
	并且其生成命令是：$(AS) $(ASFLAGS)

8. 链接 Object 文件的隐含规则
	<n> 目标依赖于 <n>.o, 通过运行 C 的编译器来运行链接程序生成(一般是 ld)
	其生成命令是: $(CC) $(LDFLAGS) <n>.o $(LOADLIBES) $(LDLIBS)
	这个规则对于只有一个源文件的工程有效, 同时也对多个 Object 文件(由不同的源文件生成)的也有效

	x : y.o z.o
	并且 x.c 、y.c 和 z.c 都存在时, 隐含规则将执行如下命令:
	cc -c x.c -o x.o
	cc -c y.c -o y.o
	cc -c z.c -o z.o
	cc x.o y.o z.o -o x
	rm -f x.o
	rm -f y.o
	rm -f z.o

	如果没有一个源文件(如上例中的 x.c)和你的目标名字(如上例中的 x)相关联
	那么你最好写出自己的生成规则, 不然隐含规则会报错的


9. Yacc C 程序时的隐含规则
	<n>.c 的依赖文件被自动推导为 n.y (Yacc 生成的文件)
	其生成命令是: $(YACC) $(YFALGS)

10. Lex C 程序时的隐含规则
	<n>.c 的依赖文件被自动推导为 n.l(Lex 生成的文件)
	其生成命令是: $(LEX) $(LFALGS) (关于“Lex”的细节请查看相关资料)

11. Lex Ratfor 程序时的隐含规则
	<n>.r 的依赖文件被自动推导为 n.l (Lex 生成的文件)
	其生成命令是: $(LEX) $(LFALGS)

12. 从 C 程序, Yacc 文件或 Lex 文件创建 Lint 库的隐含规则
	<n>.ln(lint 生成的文件)的依赖文件被自动推导为 n.c
	其生成命令是：$(LINT) $(LINTFALGS) $(CPPFLAGS) -i
	对于 <n>.y 和 <n>.l 也是同样的规则










# 隐含规则使用的变量
# 在隐含规则中的命令中, 基本上都是使用了一些预先设置的变量
# 你可以在你的 makefile 中改变这些变量的值, 或是在make的命令行中传入这些值
# 或是在你的环境变量中设置这些值, 无论怎么样, 只要设置了这些特定的变量, 那么其就会对隐含规则起作用
# 当然你也可以利用make的 -R 或 --no–builtin-variables 参数来取消你所定义的变量对隐含规则的作用

# 举例
# 编译 C 程序的隐含规则的命令是 $(CC) -c $(CFLAGS) $(CPPFLAGS)
# 如果把变量 $(CC) 重定义成 gcc, 把变量 $(CFLAGS) 重定义成 -g
# 则隐含规则中的命令全部会以 gcc -c -g $(CPPFLAGS) 的样子来执行了










# 关于命令的变量
AR : 		函数库打包程序, 默认命令是 ar
AS : 		汇编语言编译程序, 默认命令是 as
CC : 		C 语言编译程序, 默认命令是 cc
CXX : 		C++ 语言编译程序, 默认命令是 g++
CO : 		从 RCS 文件中扩展文件程序, 默认命令是 co
CPP : 		C 程序的预处理器(输出是标准输出设备), 默认命令是 $(CC) -E
FC : 		Fortran 和 Ratfor 的编译器和预处理程序, 默认命令是 f77
GET : 		从 SCCS 文件中扩展文件的程序, 默认命令是 get
LEX : 		Lex 方法分析器程序(针对于 C 或 Ratfor), 默认命令是 lex
PC : 		Pascal 语言编译程序, 默认命令是 pc
YACC : 		Yacc 文法分析器(针对于 C 程序), 默认命令是 yacc
YACCR : 	Yacc 文法分析器(针对于 Ratfor 程序)默认命令是 yacc -r
MAKEINFO : 	转换 Texinfo 源文件(.texi)到 Info 文件程序, 默认命令是 makeinfo
TEX : 		从 TeX 源文件创建 TeX DVI 文件的程序, 默认命令是 tex
TEXI2DVI : 	从 Texinfo 源文件创建军 TeX DVI 文件的程序, 默认命令是 texi2dvi
WEAVE : 	转换 Web 到 TeX 的程序, 默认命令是 weave
CWEAVE : 	转换 C Web 到 TeX 的程序, 默认命令是 cweave
TANGLE : 	转换 Web 到 Pascal 语言的程序, 默认命令是 tangle
CTANGLE : 	转换 C Web 到 C, 默认命令是 ctangle
RM : 		删除文件命令, 默认命令是 rm -f









# 关于命令参数的变量
ARFLAGS : 	函数库打包程序 AR 命令的参数。默认值是 rv
ASFLAGS : 	汇编语言编译器参数(当明显地调用 .s 或 .S 文件时)
CFLAGS : 	C 语言编译器参数
CXXFLAGS : 	C++ 语言编译器参数
COFLAGS : 	RCS 命令参数
CPPFLAGS : 	C 预处理器参数(C 和 Fortran 编译器也会用到)
FFLAGS : 	Fortran 语言编译器参数
GFLAGS : 	SCCS "get"程序参数
LDFLAGS : 	链接器参数(如ld)
LFLAGS : 	Lex 文法分析器参数
PFLAGS : 	Pascal 语言编译器参数
RFLAGS : 	Ratfor 程序的 Fortran 编译器参数
YFLAGS : 	Yacc 文法分析器参数









# 隐含规则链
# "为了实现目标 .o, 尝试寻找 .c, 没有则继续尝试找 .y"
# make 会努力自动推导生成目标的一切方法
# 不管中间目标有多少其都会执着地把所有的隐含规则和你书写的规则全部合起来分析, 努力达到目标


# 在默认情况下, 对于中间目标它和一般的目标有两个地方所不同
# 第一个不同是除非中间的目标不存在, 才会引发中间规则
# 第二个不同的是, 只要目标成功产生, 那么, 产生最终目标过程中, 所产生的中间目标文件会被以 rm -f 删除

# 通常一个被 makefile 指定成目标或是依赖目标的文件不能被当作中介
# 然而可以明显地说明一个文件或是目标是中介目标
# 你可以使用伪目标 .INTERMEDIATE 来强制声明(如: .INTERMEDIATE :mid)

# 同样也可以阻止 make 自动删除中间目标
# 要做到这一点可以使用伪目标 .SECONDARY 来强制声明(如: .SECONDARY : sec)
# 还可以把你的目标, 以模式的方式来指定(如: %.o)成伪目标 .PRECIOUS的依赖目标, 以保存被隐含规则所生成的中间文件

# 禁止同一个目标出现两次或两次以上, 防止无限递归
# Make 会优化一些特殊的隐含规则, 而不生成中间文件, 比如 cc –o foo foo.c (跳过foo.o)










# 模式规则
# 模式规则中, 至少在规则的目标定义中要包含%, 否则, 就是一般的规则
# 目标中的%定义表示对文件名的匹配, %表示长度任意的非空字符串
%.o : %.c ; <command ......>;  # 指出了怎么从所有的 .c 文件生成相应的 .o 文件的规则


# 举例 1
%.o : %.c
	$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
    # $@ 表示所有的目标的挨个值, $< 表示了所有依赖目标的挨个值


# 举例 2
%.tab.c %.tab.h: %.y
	bison -d $<
# 把所有的 .y 文件都以 bison -d <n>.y 执行, 然后生成 <n>.tab.c 和 <n>.tab.h 文件



# 自动化变量
$@	表示规则中的目标文件集, 在模式规则中, 如果有多个目标,那么 $@ 就是匹配于目标中模式定义的集合
$% 	仅当目标是函数库文件中, 表示规则中的目标成员名
	例如一个目标是 foo.a(bar.o), 那么$% 就是 bar.o, $@ 就是 foo.a
	如果目标不是函数库文件(Unix 下是 .a, Windows下是 .lib)那么其值为空
$< 	依赖目标中的第一个目标名字, 如果依赖目标是以模式(即 % )定义的
	那么 $< 将是符合模式的一系列的文件集, 注意是一个一个取出来的
$? 	所有比目标新的依赖目标的集合, 以空格分隔
$^ 	所有的依赖目标的集合, 以空格分隔
	如果在依赖目标中有多个重复的那么这个变量会去除重复的依赖目标只保留一份
$+	这个变量很像 $^, 也是所有依赖目标的集合, 只是它不去除重复的依赖目标
$* 	这个变量表示目标模式中 % 及其之前的部分
	如果目标是 dir/a.foo.b, 并且目标的模式是a.%.b
	那么, $* 的值就是 dir/a.foo, 这个变量对于构造有关联的文件名是比较有趣
	如果目标中没有模式的定义, 那么 $* 也就不能被推导出, 但是如果目标文件的后缀是 make 所识别的
	那么 $* 就是除了后缀的那一部分
	例如目标是 foo.c, 因为 .c 是 make 所能识别的后缀名, 所以 $* 的值就是 foo
	这个特性是 GNU make 的, 很有可能不兼容于其它版本的 make
	所以你应该尽量避免使用 $*, 除非是在隐含规则或是静态模式中
	如果目标中的后缀是 make 所不能识别的, 那么 $* 就是空值


($@, $<, $%, $*)在扩展时只会有一个文件, 而另三个的值是一个文件列表

当你希望只对更新过的依赖文件进行操作时, $? 在显式规则中很有用, 例如假设有一个函数库文件叫 lib
其由其它几个 object 文件更新, 那么把 object 文件打包的比较有效率的 Makefile 规则是
lib : foo.o bar.o lose.o win.o
	ar r lib $?


D 的含义就是 Directory就是目录
F 的含义就是 File就是文件

下面是对于上面的七个变量分别加上 D 或是 F 的含义
$(@D) 			表示 $@ 的目录部分(不以斜杠作为结尾)
				如果 $@ 值是 dir/foo.o, 那么 $(@D) 就是 dir
				而如果 $@ 中没有包含斜杠的话, 其值就是 (当前目录)
$(@F) 			表示 $@ 的文件部分, 如果 $@ 值是 dir/foo.o
				那么 $(@F) 就是 foo.o, $(@F) 相当于函数$(notdir $@)
$(*D), $(*F) 	和上面所述的同理，也是取文件的目录部分和文件部分
				对于上面的那个例子, $(*D) 返回 dir, 而 $(*F) 返回 foo
$(%D), $(%F) 	分别表示了函数包文件成员的目录部分和文件部分
				这对于形同 archive(member) 形式的目标中的 member 中包含了不同的目录很有用
$(<D), $(<F) 	分别表示依赖文件的目录部分和文件部分
$(^D), $(^F) 	分别表示所有依赖文件的目录部分和文件部分(无相同的)
$(+D), $(+F) 	分别表示所有依赖文件的目录部分和文件部分(可以有相同的)
$(?D), $(?F) 	分别表示被更新的依赖文件的目录部分和文件部分

最后想提醒一下的是, 对于 $< 为了避免产生不必要的麻烦
我们最好给 $ 后面的那个特定字符都加上圆括号, 比如$(<) 就要比 $< 要好一些









# 模式的匹配
# 一个目标的模式有一个有前缀或是后缀的 % , 或是没有前后缀直接就是一个 %
# 把 % 所匹配的内容叫做"茎"
%.o : %.c ;
# test.c -> test.o 中的 "茎" 是test

# 当一个模式匹配包含有斜杠(实际也不经常包含)的文件时
# 那么在进行模式匹配时, 目录部分会首先被移开, 然后进行匹配, 成功后, 再把目录加回去

# 例如有一个模式e%t, 文件src/eat匹配于该模式, 于是src/a就是其"茎"
# 如果这个模式定义在依赖目标中, 而被依赖于这个模式的目标中又有个模式c%r, 那么目标就是src/car










# 重载/取消内建隐含规则
%.o : %.c
	$(CC) -c $(CPPFLAGS) $(CFLAGS) -D$(date)

# 取消
%.o : %.s  # 只要不在后面写命令就行

# 同样也可以重新定义一个全新的隐含规则
# 其在隐含规则中的位置取决于你在哪里写下这个规则, 朝前的位置就靠前










# 老式风格的"后缀规则"
# 后缀规则是一个比较老式的定义隐含规则的方法, 后缀规则会被模式规则逐步地取代, 因为模式规则更强更清晰

# 双后缀规则定义了一对后缀, 目标文件的后缀和依赖目标(源文件)的后缀, 比如 .c.o相当于 %.o : %.c
# 单后缀规则只定义一个后缀, 也就是源文件的后缀, 如 .c 相当于 % : %.c

.c.o:
	$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<

# 等价于
%.o : %.c
	$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<


.c :
	gcc $< -o $@

# 等价于
% : %.c
	gcc $< -o $@


# 后缀规则不允许任何的依赖文件, 如果有依赖文件的话, 那就不是后缀规则
# 后缀规则中, 如果没有命令, 那是毫无意义的, 因为他也不会移去内建的隐含规则


# 如果而要让 make 知道一些特定的后缀,  我们可以使用伪目标 .SUFFIXES 来定义或是删除
.SUFFIXES: .hack .win

.SUFFIXES: 			# 删除默认的后缀
.SUFFIXES: .c .o .h # 定义自己的后缀

# make的参数 -r 或 -no-builtin-rules 也会使用得默认的后缀列表为空
# 而变量 SUFFIXE 被用来定义默认的后缀列表, 你可以用
# SUFFIXES来改变后缀列表, 但请不要改变变量SUFFIXE的值










# 隐含规则搜索算法
# 比如我们有一个目标叫T
# 下面是搜索目标T的规则的算法
# 请注意, 在下面我们没有提到后缀规则, 原因是所有的后缀规则在Makefile被载入内存时, 会被转换成模式规则
# 如果目标是archive(member)的函数库文件模式, 那么这个算法会被运行两次
# 第一次是找目标T, 如果没有找到的话, 那么进入第二次, 第二次会把member当作T来搜索

1. 把 T 的目录部分分离出来叫 D, 而剩余部分叫 N (如: 如果 T 是 src/foo.o, 那么 D 就是src/, N 就是 foo.o)
2. 创建所有匹配于 T 或是 N 的模式规则列表
3. 如果在模式规则列表中有匹配所有文件的模式, 如 %, 那么从列表中移除其它的模式
4. 移除列表中没有命令的规则
5. 对于第一个在列表中的模式规则:
	1. 推导其"茎"S, S 应该是 T 或是 N 匹配于模式中 % 非空的部分
	2. 计算依赖文件, 把依赖文件中的 % 都替换成"茎"S, 如果目标模式中没有包含斜框字符
		而把 D 加在第一个依赖文件的开头
	3. 测试是否所有的依赖文件都存在或是理当存在(如果有一个文件被定义成另外一个规则的目
		标文件, 或者是一个显式规则的依赖文件，那么这个文件就叫"理当存在")
	4. 如果所有的依赖文件存在或是理当存在, 或是就没有依赖文件, 那么这条规则将被采用, 退出该算法
6. 如果经过第 5 步, 没有模式规则被找到, 那么就做更进一步的搜索, 对于存在于列表中的第一个模式规则:
	1. 如果规则是终止规则, 那就忽略它, 继续下一条模式规则
	2. 计算依赖文件(同第 5 步)
	3. 测试所有的依赖文件是否存在或是理当存在
	4. 对于不存在的依赖文件, 递归调用这个算法查找他是否可以被隐含规则找到
	5. 如果所有的依赖文件存在或是理当存在, 或是就根本没有依赖文件, 那么这条规则被采用, 退出该算法
	6. 如果没有隐含规则可以使用, 查看 .DEFAULT 规则, 如果有, 采用, 把 .DEFAULT 的命令给 T 使用
